﻿using UnityEngine;
using Unity.Entities;
using Unity.Jobs;
using Unity.Mathematics;
using Unity.Transforms;

namespace Jinndev.ECS {

    /// <summary>
    /// 根据摄像机视野，添加或移除Culled<br/>
    /// 开启条件 1:设置camera, 2:Entity带有Culling
    /// </summary>
    //[UpdateInGroup(typeof(SimulationSystemGroup), OrderLast = true)]
    [AlwaysSynchronizeSystem]
    public class CullingSystem : JobComponentSystem {

        [UpdateAfter(typeof(CullingSystem))]
        public class ECB : EntityCommandBufferSystem { }

        private ECB commandBufferSystem;

        private EntityQuery newCullingQuery;

        public Camera camera;
        public Vector2 viewBuffer = new Vector3(100, 100); // 视野边缘缓冲区大小
        public float refreshInterval = 0.2f; // 自动刷新间隔，<=0则不自动刷新

        private float4 lastViewBounds = float4.zero;
        private Vector3 lastCameraPosition = Vector3.zero;
        private float lastCameraOrthographicSize = 0;

        private bool needRefresh;
        private float timer;

        protected override void OnCreate() {
            commandBufferSystem = World.GetOrCreateSystem<ECB>();

            newCullingQuery = GetEntityQuery(new EntityQueryDesc {
                All = new ComponentType[] {
                    ComponentType.ReadOnly<Culling>(),
                    ComponentType.ReadOnly<LocalToWorld>()
                },
                None = new ComponentType[] {
                    ComponentType.ReadOnly<CullingEnabled>()
                }
            });
        }

        protected override JobHandle OnUpdate(JobHandle inputDeps) {
            if(camera == null) {
                return inputDeps;
            }

            EntityManager.AddComponent<CullingEnabled>(newCullingQuery);

            if (refreshInterval > 0) {
                timer -= Time.DeltaTime;
                if (timer <= 0) {
                    needRefresh = true;
                }
            }
            
            bool viewChanged = CalculateViewBounds();

            if(needRefresh || viewChanged || newCullingQuery.CalculateEntityCount() > 0) {
                needRefresh = false;
                timer = refreshInterval;

                //JobHandle inputDepsAdd = AddDisable(inputDeps);
                //JobHandle inputDepsRemove = RemoveDisable(inputDeps);
                //JobHandle.CompleteAll(ref inputDepsAdd, ref inputDepsRemove);

                AddDisable(inputDeps);
                RemoveDisable(inputDeps);
            }

            return inputDeps;
        }

        private bool CalculateViewBounds() {
            if (lastCameraPosition != camera.transform.position || camera.orthographicSize != lastCameraOrthographicSize) {
                lastViewBounds = CommonUtil.GetViewBounds(camera, viewBuffer);
                lastCameraOrthographicSize = camera.orthographicSize;
                lastCameraPosition = camera.transform.position;
                return true;
            }
            return false;
        }

        public void RequestRefresh() {
            needRefresh = true;
        }

        /// <summary>
        /// 将不带有Culled但离开视野的Entity，通过添加Culled标记剔除
        /// </summary>
        private JobHandle AddDisable(JobHandle inputDeps) {
            EntityCommandBuffer ecb = commandBufferSystem.CreateCommandBuffer();
            EntityCommandBuffer.ParallelWriter ecbWritter = ecb.AsParallelWriter();
            float4 viewBounds = lastViewBounds;

            JobHandle inputDepsAdd = Entities.WithAll<Culling, CullingEnabled>().WithNone<Culled>().ForEach((Entity entity, int entityInQueryIndex, in LocalToWorld localToWorld) => {
                float3 position = localToWorld.Position;
                if (CommonUtil.OutOfRange(position, viewBounds)) {
                    ecbWritter.AddComponent<Culled>(entityInQueryIndex, entity);
                }
            }).Schedule(inputDeps);
            commandBufferSystem.AddJobHandleForProducer(inputDepsAdd);
            inputDepsAdd.Complete();

            ecb.Playback(EntityManager);
            ecb.ShouldPlayback = false;

            return inputDepsAdd;
        }

        /// <summary>
        /// 将已经被标记剔除，但重新出现在视野内的Entity，去掉其Culled
        /// </summary>
        private JobHandle RemoveDisable(JobHandle inputDeps) {
            EntityCommandBuffer ecb = commandBufferSystem.CreateCommandBuffer();
            EntityCommandBuffer.ParallelWriter ecbWritter = ecb.AsParallelWriter();
            float4 viewBounds = lastViewBounds;

            JobHandle inputDepsRemove = Entities.WithAll<Culling, CullingEnabled>().WithAll<Culled>().ForEach((Entity entity, int entityInQueryIndex, in LocalToWorld localToWorld) => {
                float3 position = localToWorld.Position;
                if (CommonUtil.InOfRange(position, viewBounds)) {
                    ecbWritter.RemoveComponent<Culled>(entityInQueryIndex, entity);
                }
            }).Schedule(inputDeps);
            commandBufferSystem.AddJobHandleForProducer(inputDepsRemove);
            inputDepsRemove.Complete();

            ecb.Playback(EntityManager);
            ecb.ShouldPlayback = false;

            return inputDepsRemove;
        }

    }

}

